package com.lincs.web.part1.test;

import com.lincs.web.part1.mybatis.framework.config.BoundSql;
import com.lincs.web.part1.mybatis.framework.config.Configuration;
import com.lincs.web.part1.mybatis.framework.config.MappedStatement;
import com.lincs.web.part1.mybatis.framework.config.ParameterMapping;
import com.lincs.web.part1.mybatis.framework.sqlnode.*;
import com.lincs.web.part1.mybatis.framework.sqlsource.DynamicSqlSource;
import com.lincs.web.part1.mybatis.framework.sqlsource.RawSqlSource;
import com.lincs.web.part1.mybatis.framework.sqlsource.SqlSource;
import com.lincs.web.part1.mybatis.po.User;
import org.apache.commons.dbcp.BasicDataSource;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * <p>标题：</p>
 * <p>功能：</p>
 * <pre>
 * 其他说明：
 * </pre>
 * <p>作者：林昌胜</p>
 * <p>审核：</p>
 * <p>重构：</p>
 * <p>创建日期：2020-08-04-21:05</p>
 * 查看帮助：<a href="" target="_blank"></a>
 */
public class MybatisV2
{
    private Configuration configuration;
    private String        namespace;
    private boolean       isDynamic = false;
    private static final String DB_SQL_PRE = "db.sql.";
    private static final String DB_SQL_COLUMN = ".columnnames";
    private static final String DB_SQL_RESULTTYPE = ".resulttype";

    @Test
    public void mybatisTest() {
        //1:加载properties配置文件中的内容
        loadXML("mybatis-config.xml");
//        System.out.println("根据ID查询用户");
//        List<User> users=findUser("queryUserById",2);
        System.out.println("根据用户名查询用户");
        List<User> users = findUser("test.queryUserByParams2","男");
        /*System.out.println("根据用户名和性别查询用户");
        Map<String,Object> param = new HashMap<>();
        param.put("username","万年老腊肉");
        param.put("sex","男");
        List<User> users = findUser("test.queryUserByParam        configuration=new Configuration();//实例化
s",param);*/
        if(users!=null&&users.size()>0){
            for(int i=0;i<users.size();i++){
                User vo=users.get(i);
                System.out.println(i+"--用户信息：【id-"+vo.getId()+",username-"+vo.getUsername()+"】");
            }
        }
    }
    //加载XML配置文件
    private void loadXML(String filePath) {
        //根据XML路径加载xml文件流
        InputStream inputStream = getResourceAsStream(filePath);
        //获取Document
        Document document=getDocument(inputStream);
        //根据xml语义进行解析
        parseConfiguration(document.getRootElement());
    }

    private void parseConfiguration(Element rootElement)
    {
        Element environments =rootElement.element("environments");
        parseEnvironments(environments);
        Element mappers=rootElement.element("mappers");
        parseMappers(mappers);
    }

    /**
     * 解析全局配置文件中的mappers标签
     * @param mappers <mappers></mappers>
     */
    private void parseMappers(Element mappers)
    {
        List<Element> list=mappers.elements("mapper");
        for(Element element:list){
            String resource=element.attributeValue("resource");
            // 根据xml的路径，获取对应的输入流
            InputStream inputStream=getResourceAsStream(resource);
            // 将流对象，转换成Document对象
            Document document=getDocument(inputStream);
            // 针对Document对象，按照Mybatis的语义去解析Document
            parseMapper(document.getRootElement());
        }
    }

    /**
     * 解析映射文件的mapper信息
     * @param rootElement <mapper></mapper>
     */
    private void parseMapper(Element rootElement)
    {
        // statementid是由namespace+statement标签的id值组成的。
        namespace=rootElement.attributeValue("namespace");
        // TODO 获取动态SQL标签，比如<sql>
        // TODO 获取其他标签
        List<Element> selectElements=rootElement.elements("select");
        for(Element selectElement:selectElements){
            parseStatementElement(selectElement);
        }
    }

    /**
     * 解析映射文件中的select标签
     * @param selectElement <select></select>
     */
    private void parseStatementElement(Element selectElement)
    {
        String statementId=selectElement.attributeValue("id");
        if(statementId==null||"".equals(statementId)){
            return;
        }
        // 一个CURD标签对应一个MappedStatement对象
        // 一个MappedStatement对象由一个statementId来标识，所以保证唯一性
        // statementId = namespace + "." + CRUD标签的id属性
        statementId = namespace + "." + statementId;

        // 注意：parameterType参数可以不设置也可以不解析
        /* String parameterType = selectElement.attributeValue("parameterType");
        Class<?> parameterClass = resolveType(parameterType);*/
        String resultType=selectElement.attributeValue("resultType");
        Class<?> returnClass=resolveTyepe(resultType);

        String statementType=selectElement.attributeValue("statementType");
        statementType=statementType==null|statementType==""?"prepared":statementType;
        //TODO SqlSource和SqlNode的封装过程
        SqlSource sqlSource=createSqlSource(selectElement);
        // TODO 建议使用构建者模式去优化
        MappedStatement mappedStatement=new MappedStatement(statementId,returnClass,statementType,sqlSource);
        configuration.addMappedStatement(statementId,mappedStatement);
    }

    private SqlSource createSqlSource(Element selectElement)
    {
        //TODO 其他子标签的解析处理
        SqlSource sqlSource=parseScriptNode(selectElement);
        return sqlSource;
    }

    private SqlSource parseScriptNode(Element selectElement)
    {
        //解析所有SQL节点，最终封装到MixedSqlNode中
        SqlNode mixedSqlNode=parseDynamicTags(selectElement);
        SqlSource sqlSource;
        //如果带有${}或者动态SQL标签
        if(isDynamic){
            sqlSource=new DynamicSqlSource(mixedSqlNode);
        }else {
            sqlSource=new RawSqlSource(mixedSqlNode);
        }
        return sqlSource;
    }

    private SqlNode parseDynamicTags(Element selectElement)
    {
        List<SqlNode> sqlNodes=new ArrayList<>();
        //获取select标签的子元素 ：文本类型或者Element类型
        int nodeCounts=selectElement.nodeCount();
        for(int i=0;i<nodeCounts;i++){
            Node node=selectElement.node(i);
            if(node instanceof Text){
                String text=node.getText();
                if(text==null){
                    continue;
                }
                if("".equals(text.trim())){
                    continue;
                }
                // 先将sql文本封装到TextSqlNode中
                TextSqlNode textSqlNode=new TextSqlNode(text.trim());
                if(textSqlNode.isDynamic()){
                    sqlNodes.add(textSqlNode);
                    isDynamic=true;
                }else {
                    sqlNodes.add(new StaticTextSqlNode(text.trim()));
                }
            }else if(node instanceof Element){
                isDynamic=true;
                Element element=(Element)node;
                String name=element.getName();
                if("if".equals(name)){
                    String test=element.attributeValue("test");
                    //递归去解析子元素
                    SqlNode sqlNode=parseDynamicTags(element);
                    IfSqlNode ifSqlNode=new IfSqlNode(test,sqlNode);
                    sqlNodes.add(ifSqlNode);
                }else{
                    //TODO
                }
            }else {
                //TODO
            }
        }
        return new MixedSqlNode(sqlNodes);
    }

    /**
     * 根据全限定名获取Class对象
     * @param clazzPath
     * @return
     */
    private Class<?> resolveTyepe(String clazzPath)
    {
        Class<?> clazz= null;
        try
        {
            clazz = Class.forName(clazzPath);
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        return clazz;
    }

    private void parseEnvironments(Element environments)
    {
        String sourceKey=environments.attributeValue("default");
       List<Element> elements= environments.elements("environment");
       for(Element element:elements){
           String id=element.attributeValue("id");
           if(sourceKey.equals(id)){
               parseDataSource(element.element("dataSource"));
           }
       }
    }

    private void parseDataSource(Element dataSource)
    {
        String type=dataSource.attributeValue("type");
        if("DBCP".equals(type)){
            BasicDataSource ds = new BasicDataSource();
            Properties properties=parseProperties(dataSource);
            ds.setDriverClassName(properties.getProperty("db.driver"));
            ds.setUrl(properties.getProperty("db.url"));
            ds.setUsername(properties.getProperty("db.username"));
            ds.setPassword(properties.getProperty("db.password"));
            configuration.setDataSource(ds);
        }
    }

    private Properties parseProperties(Element dataSource)
    {
        Properties properties=new Properties();
        List<Element> propertys=dataSource.elements("property");
        for(Element property:propertys){
            String name=property.attributeValue("name");
            String value=property.attributeValue("value");
            properties.put(name,value);
        }
        return properties;
    }

    private Document getDocument(InputStream inputStream)
    {
        SAXReader reader=new SAXReader();
        Document document= null;
        try
        {
            document = reader.read(inputStream);
        } catch (DocumentException e)
        {
            e.printStackTrace();
        }
        return document;
    }

    private InputStream getResourceAsStream(String filePath)
    {
        return this.getClass().getClassLoader().getResourceAsStream(filePath);
    }

    public <T> List<T> findUser(String statementId, Object params) {
        List<T> results=new ArrayList<T>();
        Connection conn = null;
        Statement state = null;
        ResultSet rs = null;
        try {
            // 获取statement相关的信息MappedStatement
            MappedStatement mappedStatement=configuration.getMappedStatementById(statementId);
            //获取连接
            conn = getConnection();
            // TODO SQL的获取(SqlSource和SqlNode的处理流程)
            SqlSource sqlSource=mappedStatement.getSqlSource();
            //触发SqlSource和SqlNode的解析处理流程
            BoundSql boundSql=sqlSource.getBoundSql(params);
            String sql=boundSql.getSql();
            // 创建statement
            state=createStatement(mappedStatement,sql,conn);
            //设置参数
            setParameters(params,state,boundSql);
            // 执行statement
            rs=executeQuery(state);
            handlerResultSet(rs,mappedStatement,results);

        } catch (Exception e) {
            System.out.println("数据库访问异常！");
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (state != null) {
                    state.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                System.out.println("回收资源，关闭链接时发生异常");
                e.printStackTrace();
            }
        }
        return results;
    }

    private <T> void handlerResultSet(ResultSet rs, MappedStatement mappedStatement, List<T> results) throws Exception
    {
        //获取返回结果类型--路径
        Class clazz=mappedStatement.getResultTypeClass();
        //通过构造函数创建对象--返回对象类型
        Constructor<?> constructor=clazz.getDeclaredConstructor();
        Object result=null;
        while (rs.next()) {
            result=constructor.newInstance();
            //结果集中列信息
            ResultSetMetaData metaData=rs.getMetaData();
            int columnCount=metaData.getColumnCount();//列数
            for(int i=0;i<columnCount;i++){
                String columnName=metaData.getColumnName(i+1);
                //通过反射给指定列对应的属性名称赋值，列名必须与属性名一致
                Field field=clazz.getDeclaredField(columnName);
                //暴力破解，破坏封装，可以访问私有成员
                field.setAccessible(true);
                field.set(result,rs.getObject(columnName));
            }
            results.add((T) result);
        }
    }

    private ResultSet executeQuery(Statement state) throws Exception
    {
        ResultSet rs=null;
        if(state instanceof PreparedStatement){
            PreparedStatement preparedStatement=(PreparedStatement)state;
            rs=preparedStatement.executeQuery();
        }
        return rs;
    }

    private void setParameters(Object params, Statement state, BoundSql boundSql) throws Exception
    {
        if(state instanceof PreparedStatement){
            PreparedStatement preparedStatement=(PreparedStatement)state;
            if (params instanceof Integer || params instanceof String) {
                preparedStatement.setObject(1, params);
            } else if (params instanceof Map) {
                Map<String, Object> paramsMap=(Map<String,Object>)params;
                // TODO 需要解析#{}之后封装的参数集合List<ParameterMapping>
                List<ParameterMapping> parameterMappings=boundSql.getParameterMappings();
                for(int i=0;i<parameterMappings.size();i++){
                    ParameterMapping parameterMapping=parameterMappings.get(i);
                    String name=parameterMapping.getName();
                    // map集合中的key和要映射的参数名称要一致
                    Object value=paramsMap.get(name);
                    preparedStatement.setObject(i+1,value);
                }
            } else {
                System.out.println("暂不支持该入参类型");
                throw new RuntimeException("暂不支持该入参类型");
            }
        }else{
            //TODO
        }
    }

    private Statement createStatement(MappedStatement mappedStatement, String sql, Connection conn) throws Exception
    {
        String statementType=mappedStatement.getStatementType();
        if("prepared".equals(statementType)){
            return conn.prepareStatement(sql);
        }else {
            //TODO
        }
        return null;
    }

    private Connection getConnection() throws Exception
    {
        return configuration.getDataSource().getConnection();
    }
}